#include <string.h>

namespace tb
{

	// ================================
	// Constructor
	// ================================

	TB_INLINE ColorRGB::ColorRGB()
	{
		r = g = b = 0.f;
		a = 1.f;
	}
	TB_INLINE ColorRGB::ColorRGB(float a_RGB, float a_Alpha/* = 1.f*/)
	{
		r = g = b = a_RGB;
		a = a_Alpha;
	}
	TB_INLINE ColorRGB::ColorRGB(float a_R, float a_G, float a_B, float a_A/* = 1.f*/)
	{
		r = a_R;
		g = a_G;
		b = a_B;
		a = a_A;
	}
	TB_INLINE ColorRGB::ColorRGB(Pixel a_Color)
	{
		r = (float)((a_Color & TB_COLOR_MASK_R) >> TB_COLOR_SHIFT_R) / TB_COLOR_BITMASK_FR;
		g = (float)((a_Color & TB_COLOR_MASK_G) >> TB_COLOR_SHIFT_G) / TB_COLOR_BITMASK_FG;
		b = (float)((a_Color & TB_COLOR_MASK_B) >> TB_COLOR_SHIFT_B) / TB_COLOR_BITMASK_FB;
		a = (float)((a_Color & TB_COLOR_MASK_A) >> TB_COLOR_SHIFT_A) / TB_COLOR_BITMASK_FA;
	}
	TB_INLINE ColorRGB::ColorRGB(const ColorRGB& a_Other)
	{
		r = a_Other.r;
		g = a_Other.g;
		b = a_Other.b;
		a = a_Other.a;
	}
	TB_INLINE ColorRGB::~ColorRGB()
	{

	}

	// ================================
	// Conversion
	// ================================

	TB_INLINE ColorRGB& ColorRGB::FromHTML(const char* a_HTMLColor)
	{
		char* data = (char*)a_HTMLColor;

		// a valid color is #FFFF00
		if (strlen(data) != 7 || *data != '#') { return *this; }

		// skip the #
		data++;

		int number[2];
		int current = 0;
		int color = 0;
		while (*data)
		{
			// A - F = 10 - 15
			if (*data >= 65)      { number[current] = *data - 55; }
			// 0 - 9 = 1 - 10
			else if (*data >= 48) { number[current] = *data - 48; }

			current++;
			if (current == 2)
			{
				(&r)[color] = (float)((number[0] * 16) + number[1]) / 255.f;
				if (color++ == 3) { break; }
				current = 0;
			}

			data++;
		}

		return *this;
	}

	TB_INLINE ColorRGB& ColorRGB::FromPixel(const Pixel& a_Pixel)
	{
		r = (float)((a_Pixel & TB_COLOR_MASK_R) >> TB_COLOR_SHIFT_R) / TB_COLOR_BITMASK_FR;
		g = (float)((a_Pixel & TB_COLOR_MASK_G) >> TB_COLOR_SHIFT_G) / TB_COLOR_BITMASK_FG;
		b = (float)((a_Pixel & TB_COLOR_MASK_B) >> TB_COLOR_SHIFT_B) / TB_COLOR_BITMASK_FB;
		a = (float)((a_Pixel & TB_COLOR_MASK_A) >> TB_COLOR_SHIFT_A) / TB_COLOR_BITMASK_FA;

		return *this;
	}

	TB_INLINE ColorRGB& ColorRGB::ToOpenGLColor(float* a_Target)
	{
		a_Target[0] = r;
		a_Target[1] = g;
		a_Target[2] = b;
		a_Target[3] = a;

		return *this;
	}

	TB_INLINE Pixel ColorRGB::ToPixel() const
	{
		Pixel R, G, B, A;

		if (r > 1.f)		{ R = TB_COLOR_MASK_R; }
		else if (r < 0.f)	{ R = 0; }
		else				{ R = (Pixel)(r * TB_COLOR_BITMASK_FR) << TB_COLOR_SHIFT_R; }

		if (g > 1.f)		{ G = TB_COLOR_MASK_G; }
		else if (g < 0.f)	{ G = 0; }
		else				{ G = (Pixel)(g * TB_COLOR_BITMASK_FG) << TB_COLOR_SHIFT_G; }

		if (b > 1.f)		{ B = TB_COLOR_MASK_B; }
		else if (b < 0.f)	{ B = 0; }
		else				{ B = (Pixel)(b * TB_COLOR_BITMASK_FB) << TB_COLOR_SHIFT_B; }

		if (a > 1.f)		{ A = TB_COLOR_MASK_A; }
		else if (a < 0.f)	{ A = 0; }
		else				{ A = (Pixel)(a * TB_COLOR_BITMASK_FA) << TB_COLOR_SHIFT_A; }

		return (R + G + B + A);
	}

	// ================================
	// Assignment & Access
	// ================================

	TB_INLINE ColorRGB& ColorRGB::operator = (Pixel a_Color)
	{
		r = (float)((a_Color & TB_COLOR_MASK_R) >> TB_COLOR_SHIFT_R) / TB_COLOR_BITMASK_FR;
		g = (float)((a_Color & TB_COLOR_MASK_G) >> TB_COLOR_SHIFT_G) / TB_COLOR_BITMASK_FG;
		b = (float)((a_Color & TB_COLOR_MASK_B) >> TB_COLOR_SHIFT_B) / TB_COLOR_BITMASK_FB;
		a = (float)((a_Color & TB_COLOR_MASK_A) >> TB_COLOR_SHIFT_A) / TB_COLOR_BITMASK_FA;

		return *this;
	}

	TB_INLINE ColorRGB& ColorRGB::operator = (const ColorRGB& a_Other)
	{
		r = a_Other.r;
		g = a_Other.g;
		b = a_Other.b;
		a = a_Other.a;

		return *this;
	}

	TB_INLINE ColorRGB& ColorRGB::operator = (float a_Scalar)
	{
		r = a_Scalar;
		g = a_Scalar;
		b = a_Scalar;

		return *this;
	}

	TB_INLINE float& ColorRGB::operator [] (int a_Index)
	{
		return (&r)[a_Index];
	}

	TB_INLINE float ColorRGB::operator [] (int a_Index) const
	{
		return (&r)[a_Index];
	}

	// ================================
	// Operators
	// ================================

	TB_INLINE ColorRGB& ColorRGB::operator += (const ColorRGB& a_Other)
	{
		r += a_Other.r;
		g += a_Other.g;
		b += a_Other.b;

		return *this;
	}
	TB_INLINE ColorRGB& ColorRGB::operator += (float a_Scalar)
	{
		r += a_Scalar;
		g += a_Scalar;
		b += a_Scalar;

		return *this;
	}
	TB_INLINE ColorRGB& ColorRGB::operator += (Pixel a_Color)
	{
		r += (float)((a_Color & TB_COLOR_MASK_R) >> TB_COLOR_SHIFT_R) / TB_COLOR_BITMASK_FR;
		g += (float)((a_Color & TB_COLOR_MASK_G) >> TB_COLOR_SHIFT_G) / TB_COLOR_BITMASK_FG;
		b += (float)((a_Color & TB_COLOR_MASK_B) >> TB_COLOR_SHIFT_B) / TB_COLOR_BITMASK_FB;
		a += (float)((a_Color & TB_COLOR_MASK_A) >> TB_COLOR_SHIFT_A) / TB_COLOR_BITMASK_FA;

		return *this;
	}

	TB_INLINE ColorRGB ColorRGB::operator + (const ColorRGB& a_Other) const
	{
		ColorRGB result(r + a_Other.r, g + a_Other.g, b + a_Other.b, a + a_Other.a);
		return result;
	}
	TB_INLINE ColorRGB ColorRGB::operator + (float a_Scalar) const
	{
		ColorRGB result(r + a_Scalar, g + a_Scalar, b + a_Scalar, a);
		return result;
	}

	TB_INLINE ColorRGB& ColorRGB::operator -= (const ColorRGB& a_Other)
	{
		r -= a_Other.r;
		g -= a_Other.g;
		b -= a_Other.b;

		return *this;
	}
	TB_INLINE ColorRGB& ColorRGB::operator -= (float a_Scalar)
	{
		r -= a_Scalar;
		g -= a_Scalar;
		b -= a_Scalar;

		return *this;
	}
	TB_INLINE ColorRGB ColorRGB::operator - (const ColorRGB& a_Other) const
	{
		ColorRGB result(r - a_Other.r, g - a_Other.g, b - a_Other.b, a);

		return result;
	}
	TB_INLINE ColorRGB ColorRGB::operator - (float a_Scalar) const
	{
		ColorRGB result(r - a_Scalar, g - a_Scalar, b - a_Scalar, a);

		return result;
	}

	TB_INLINE ColorRGB& ColorRGB::operator *= (const ColorRGB& a_Other)
	{
		r *= a_Other.r;
		g *= a_Other.g;
		b *= a_Other.b;

		return *this;
	}
	TB_INLINE ColorRGB& ColorRGB::operator *= (float a_Scalar)
	{
		r *= a_Scalar;
		g *= a_Scalar;
		b *= a_Scalar;

		return *this;
	}

	TB_INLINE ColorRGB ColorRGB::operator * (const ColorRGB& a_Other) const
	{
		ColorRGB result(r * a_Other.r, g * a_Other.g, b * a_Other.b);

		return result;
	}
	TB_INLINE ColorRGB ColorRGB::operator * (float a_Scalar) const
	{
		ColorRGB result(r * a_Scalar, g * a_Scalar, b * a_Scalar);

		return result;
	}

	TB_INLINE ColorRGB& ColorRGB::operator /= (const ColorRGB& a_Other)
	{
		r /= a_Other.r;
		g /= a_Other.g;
		b /= a_Other.b;

		return *this;
	}
	TB_INLINE ColorRGB& ColorRGB::operator /= (float a_Scalar)
	{
		r /= a_Scalar;
		g /= a_Scalar;
		b /= a_Scalar;

		return *this;
	}

	TB_INLINE ColorRGB ColorRGB::operator / (const ColorRGB& a_Other) const
	{
		ColorRGB result(r / a_Other.r, g / a_Other.g, b / a_Other.b);

		return result;
	}
	TB_INLINE ColorRGB ColorRGB::operator / (float a_Scalar) const
	{
		ColorRGB result(r / a_Scalar, g / a_Scalar, b / a_Scalar);

		return result;
	}

	// ================================
	// Functions
	// ================================

	TB_INLINE ColorRGB& ColorRGB::Clear()
	{
		r = g = b = 0.f;
		a = 1.f;

		return *this;
	}

	TB_INLINE float ColorRGB::GetLength() const
	{
		return (r + g + b);
	}

	TB_INLINE ColorRGB& ColorRGB::Blend(const ColorRGB& a_Other, float a_Percentage)
	{
		if (a_Percentage < 0.f) 
		{ 
			return *this;
		}
		if (a_Percentage > 1.f) 
		{
			r = a_Other.r;
			g = a_Other.g;
			b = a_Other.b;
			a = a_Other.a;

			return *this;
		}

		r = r + (a_Other.r - r) * a_Percentage;
		g = g + (a_Other.g - g) * a_Percentage;
		b = b + (a_Other.b - b) * a_Percentage;
		a = a + (a_Other.a - a) * a_Percentage;

		return *this;
	}

	TB_INLINE ColorRGB ColorRGB::GetBlended(const ColorRGB& a_Other, float a_Percentage) const
	{
		ColorRGB result;

		if (a_Percentage < 0.f) 
		{ 
			return *this; 
		}
		if (a_Percentage > 1.f) 
		{
			return a_Other; 
		}

		result.r = r + (a_Other.r - r) * a_Percentage;
		result.g = g + (a_Other.g - g) * a_Percentage;
		result.b = b + (a_Other.b - b) * a_Percentage;
		result.a = a + (a_Other.a - a) * a_Percentage;

		return result;
	}

	TB_INLINE ColorRGB& ColorRGB::Normalize()
	{
		r = (r > 1.f) ? 1.f : (r < 0.f) ? 0.f : r;
		g = (g > 1.f) ? 1.f : (g < 0.f) ? 0.f : g;
		b = (b > 1.f) ? 1.f : (b < 0.f) ? 0.f : b;
		a = (a > 1.f) ? 1.f : (a < 0.f) ? 0.f : a;

		return *this;
	}

	TB_INLINE ColorRGB ColorRGB::GetNormalized() const
	{
		ColorRGB result;

		result.r = (r > 1.f) ? 1.f : (r < 0.f) ? 0.f : r;
		result.g = (g > 1.f) ? 1.f : (g < 0.f) ? 0.f : g;
		result.b = (b > 1.f) ? 1.f : (b < 0.f) ? 0.f : b;
		result.a = (a > 1.f) ? 1.f : (a < 0.f) ? 0.f : a;

		return result;
	}

	TB_INLINE ColorRGB& ColorRGB::ClampUp(float a_Upper/* = 1.f*/)
	{
		r = (r > a_Upper) ? a_Upper : r;
		g = (g > a_Upper) ? a_Upper : g;
		b = (b > a_Upper) ? a_Upper : b;
		a = (a > a_Upper) ? a_Upper : a;

		return *this;
	}

	TB_INLINE ColorRGB& ColorRGB::ClampDown(float a_Lower/* = 0.f*/)
	{
		r = (r < a_Lower) ? a_Lower : r;
		g = (g < a_Lower) ? a_Lower : g;
		b = (b < a_Lower) ? a_Lower : b;
		a = (a < a_Lower) ? a_Lower : a;

		return *this;
	}

	TB_INLINE ColorRGB& ColorRGB::Clamp(float a_Lower/* = 0.f*/, float a_Upper/* = 1.f*/)
	{
		r = (r > a_Upper) ? a_Upper : (r < a_Lower) ? a_Lower : r;
		g = (g > a_Upper) ? a_Upper : (g < a_Lower) ? a_Lower : g;
		b = (b > a_Upper) ? a_Upper : (b < a_Lower) ? a_Lower : b;
		a = (a > a_Upper) ? a_Upper : (a < a_Lower) ? a_Lower : a;

		return *this;
	}

	/****************************************
	**
	**
	**
	** ColorHSV
	**
	**
	**
	****************************************/

	TB_INLINE ColorHSV::ColorHSV()
	{
		hue = 0.f;
		saturation = 1.f;
		value = 1.f;
	}

	TB_INLINE ColorHSV::ColorHSV(float a_Hue, float a_Saturation, float a_Value)
	{
		hue = a_Hue;
		saturation = a_Saturation;
		value = a_Value;
	}

	TB_INLINE ColorHSV::ColorHSV(const ColorRGB& a_Color)
	{
		FromRGB(a_Color);
	}

	TB_INLINE ColorHSV& ColorHSV::FromRGB(const ColorRGB& a_RGB)
	{
		float rgb_min, rgb_max;

		float r = a_RGB.r;
		float g = a_RGB.g;
		float b = a_RGB.b;

		if (r < g) 
		{
			// r < b < g
			if (r < b) { rgb_min = r; rgb_max = g; }
			// b < r < g
			else       { rgb_min = b; rgb_max = g; }
		}
		else if (g < b)
		{
			// r < g < b
			if (r < g) { rgb_min = r; rgb_max = b; }
			// g < r < b
			else       { rgb_min = g; rgb_max = b; }
		}
		else
		{
			// g < b < r
			if (g < b) { rgb_min = g; rgb_max = r; }
			// b < g < r
			else       { rgb_min = b; rgb_max = r; }
		}

		value = rgb_max;
		if (value == 0) 
		{
			hue = saturation = 0;
		}
		else
		{
			float invval = 1.f / value;
			r *= invval;
			g *= invval;
			b *= invval;

			if (r < g) 
			{
				// r < b < g
				if (r < b) { rgb_min = r; rgb_max = g; }
				// b < r < g
				else       { rgb_min = b; rgb_max = g; }
			}
			else if (g < b)
			{
				// r < g < b
				if (r < g) { rgb_min = r; rgb_max = b; }
				// g < r < b
				else       { rgb_min = g; rgb_max = b; }
			}
			else
			{
				// g < b < r
				if (g < b) { rgb_min = g; rgb_max = r; }
				// b < g < r
				else       { rgb_min = b; rgb_max = r; }
			}

			saturation = rgb_max - rgb_min;
			if (saturation == 0) 
			{
				hue = 0;
			}
			else
			{
				// normalize saturation

				float length = 1 / (rgb_max - rgb_min);
				r = (r - rgb_min) * length;
				g = (g - rgb_min) * length;
				b = (b - rgb_min) * length;

				rgb_max = Math::Max(r, g, b);

				// calculate hue

				if (rgb_max == r) 
				{
					hue = 0.f + 60.f * (g - b);
					if (hue < 0.f) { hue += 360.f; }
				} 
				else if (rgb_max == g) 
				{
					hue = 120.f + 60.f * (b - r);
				} 
				else
				{
					hue = 240.f + 60.f * (r - g);
				}
			}
		}
	}

	TB_INLINE ColorRGB ColorHSV::ToRGB()
	{
		float h = (hue / 360.f) * 6.f;

		float i = Math::Floor(h);
		float f = (h - i);
		int ii = (int)i;
		// if i is even
		if (!(ii & 1)) { f = 1 - f; }
		float m = value * (1 - saturation);
		float n = value * (1 - saturation * f);

		ColorRGB result;

		switch (ii)
		{
		case 6:
		case 0:
			result.r = value;
			result.g = n;
			result.b = m;
			break;
		case 1:
			result.r = n;
			result.g = value;
			result.b = m;
			break;
		case 2:
			result.r = m;
			result.g = value;
			result.b = n;
			break;
		case 3:
			result.r = m;
			result.g = n;
			result.b = value;
			break;
		case 4:
			result.r = n;
			result.g = m;
			result.b = value;
			break;
		case 5:
			result.r = value;
			result.g = m;
			result.b = n;
			break;
		}

		return result;
	}

};